package com.bizmda.bizsip.common;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONStrFormatter;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

import javax.lang.model.type.PrimitiveType;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.concurrent.*;

/**
 * @author 史正烨
 */
@Slf4j
public class BizUtils {
    public static final ThreadLocal<BizMessage<JSONObject>> bizMessageThreadLocal = new ThreadLocal<>();
    public static final ThreadLocal<TmContext> tmContextThreadLocal = new ThreadLocal<>();
    public static final ThreadLocal<String> serviceIdThreadLocal = new ThreadLocal<>();
    private static final ExecutorService elExecutorService = ThreadUtil.newExecutor(Runtime.getRuntime().availableProcessors());

    private BizUtils() {
    }

    public static String getElStringResult(String express, Object data) throws BizException {
        String result = null;
        Future<Object> future = elExecutorService.submit(new ElThread(express, data, false));
        try {
            result = (String) future.get();
        } catch (InterruptedException e) {
            log.error("EL表达式计算被中断:{}", express, e);
            Thread.currentThread().interrupt();
            return null;
        } catch (ExecutionException e) {
            throw new BizException(BizResultEnum.OTHER_EL_EXECUTE_ERROR, e,
                    StrFormatter.format("EL表达式计算出错:{}", express));
        }
        return result;
    }

    public static Boolean getElBooleanResult(String express, Object data) throws BizException {
        Boolean result = null;
        Future<Object> future = elExecutorService.submit(new ElThread(express, data, true));
        try {
            result = (Boolean) future.get();
        } catch (InterruptedException e) {
            log.error("EL表达式计算被中断:{}", express, e);
            Thread.currentThread().interrupt();
            return false;
        } catch (ExecutionException e) {
            throw new BizException(BizResultEnum.OTHER_EL_EXECUTE_ERROR, e,
                    StrFormatter.format("EL表达式计算出错:{}", express));
        }
        return result;
    }

    public static byte[] getBytes(String str) throws BizException {
        try {
            return str.getBytes(BizConstant.DEFAULT_CHARSET_NAME);
        } catch (UnsupportedEncodingException e) {
            throw new BizException(BizResultEnum.OTHER_UNSUPPORTED_ENCODING_ERROR, e);
        }
    }

    public static String getString(byte[] bytes) throws BizException {
        try {
            return new String(bytes, BizConstant.DEFAULT_CHARSET_NAME);
        } catch (UnsupportedEncodingException e) {
            throw new BizException(BizResultEnum.OTHER_UNSUPPORTED_ENCODING_ERROR, e);
        }
    }

    public static String buildBizMessageLog(BizMessage bizMessage) {
        StringBuilder stringBuilder = new StringBuilder();
        if (!StrUtil.isEmpty(bizMessage.getTraceId())) {
            stringBuilder.append("traceId: " + bizMessage.getTraceId() + "\n");
        }
        if (!StrUtil.isEmpty(bizMessage.getParentTraceId())) {
            stringBuilder.append("parentTraceId: " + bizMessage.getParentTraceId() + "\n");
        }
        stringBuilder.append("code: " + bizMessage.getCode() + "\n");
        if (!StrUtil.isEmpty(bizMessage.getMessage())) {
            stringBuilder.append("message: " + bizMessage.getMessage() + "\n");
        }
        if (!StrUtil.isEmpty(bizMessage.getExtMessage())) {
            stringBuilder.append("extMessage: " + bizMessage.getExtMessage() + "\n");
        }
        if (bizMessage.getData() instanceof JSONObject) {
            stringBuilder.append(buildJsonLog(bizMessage.getData()));
        } else if (bizMessage.getData() instanceof byte[]) {
            stringBuilder.append(buildHexLog((byte[]) bizMessage.getData()));
        } else {
            if (bizMessage.getData() != null) {
                stringBuilder.append("<<" + bizMessage.getData().getClass().getName() + ">>:\n");
            }
            stringBuilder.append(bizMessage.getData());
        }

        return stringBuilder.toString();
    }

    public static String buildJsonLog(Object jsonObject) {
        if (jsonObject instanceof String) {
            return JSONStrFormatter.format((String) jsonObject);
        } else {
            return JSONUtil.toJsonPrettyStr(jsonObject);
        }
    }

    public static String buildHexLog(byte[] bytes) {
        byte[] rightBytes = new byte[20];
        StringBuilder stringBuilder = new StringBuilder("====+ 01-02-03-04-05-06-07-08-09-10-11-12-13-14-15-16-17-18-19-20 + ====== ASCII  ====== +\n");
        int i;
        for (i = 0; i < bytes.length; i++) {
            if (i % 20 == 0) {
                for (int j = 0; j < 20; j++) {
                    rightBytes[j] = '.';
                }
                stringBuilder.append(StrUtil.fill(String.valueOf(i), '0', 4, true));
                stringBuilder.append(':');
            }
            stringBuilder.append(' ');
            HexUtil.appendHex(stringBuilder, bytes[i], false);
            if (bytes[i] >= 0x20 || bytes[i] < 0) {
                rightBytes[i % 20] = bytes[i];
            }
            if ((i + 1) % 20 == 0) {
                stringBuilder.append(" | ");
                try {
                    appendRightBytes(stringBuilder, rightBytes);
                    stringBuilder.append(" |");
                } catch (BizException e) {
                    e.printStackTrace();
                }
                stringBuilder.append("\n");
            }
        }

        for (int j = i % 20; j < 20; j++) {
            stringBuilder.append("   ");
        }
        if (i % 20 != 0) {

            stringBuilder.append(" | ");
            try {
                appendRightBytes(stringBuilder, rightBytes);
                stringBuilder.append(" |");
            } catch (BizException e) {
                e.printStackTrace();
            }
            stringBuilder.append("\n");
        }
        return stringBuilder.toString();
    }

    private static void appendRightBytes(StringBuilder stringBuilder, byte[] bytes) throws BizException {
        char[] ch = BizUtils.getString(bytes).toCharArray();
        for (int i = 0; i < ch.length; i++) {
            stringBuilder.append(ch[i]);
            if (isChinese(ch[i])) {
                stringBuilder.append(".");
            }
        }
        return;
    }

    private static boolean isChinese(char c) {
        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);

        if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS

                || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS

                || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A

                || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B

                || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION

                || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS

                || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION) {
            return true;
        }
        return false;
    }


    public static void info(String message, Object... params) {
        if (!(log.isInfoEnabled() || log.isDebugEnabled() || log.isTraceEnabled())) {
            return;
        }
        log(message, params);
    }

    public static void debug(String message, Object... params) {
        if (!(log.isDebugEnabled() || log.isTraceEnabled())) {
            return;
        }
        log(message, params);
    }

    public static void trace(String message, Object... params) {
        if (!(log.isTraceEnabled())) {
            return;
        }
        log(message, params);
    }

    private static void log(String message, Object[] params) {
        StackTraceElement a = (StackTraceElement) Thread.currentThread().getStackTrace()[3];
        StringBuilder stringBuilder1 = new StringBuilder();
        StringBuilder stringBuilder2 = new StringBuilder();
        for (Object param : params) {
            if (param instanceof BizMessage) {
                stringBuilder1.append("<<BizMessage>>,");
                stringBuilder2.append("\n").append(BizUtils.buildBizMessageLog((BizMessage) param));
            } else if (param instanceof JSONObject) {
                stringBuilder1.append("<<JSON>>,");
                stringBuilder2.append("\n").append(BizUtils.buildJsonLog(param));
            } else if (param instanceof byte[]) {
                stringBuilder1.append("<<byte[]>>,");
                stringBuilder2.append("\n").append(BizUtils.buildHexLog((byte[]) param));
            } else {
                stringBuilder1.append(param).append(",");
            }
        }
        if (stringBuilder1.length() > 0) {
            stringBuilder1.delete(stringBuilder1.length() - 1, stringBuilder1.length());
        }
        log.debug("{}#{}-{}: {}{}", a.getClassName(),a.getMethodName(),message,stringBuilder1, stringBuilder2);
    }

    private static Object Json2Bean(Object json, Type type) throws BizException {
        Object beanObject;
        if (json instanceof JSONObject) {
            beanObject = JSONUtil.toBean((JSONObject) json, type,true);
        } else if (json instanceof JSONArray) {
            beanObject = JSONUtil.toBean((JSONArray) json, type,true);
        } else {
            beanObject = Convert.convert(type, json);
        }
        return beanObject;
    }

    public static JSONObject getParamtersTypesJSONObject(Method method,Object[] args) {
        JSONObject jsonObject = new JSONObject();
        Type[] types = method.getGenericParameterTypes();
        for (int i = 0;i<types.length;i++) {
            if (args[i] == null) {
                continue;
            }
            if (types[i] instanceof  ParameterizedType) {
                // 方法中参数为泛型不处理，用方法中的泛型来强转
                continue;
            }
            if (types[i] instanceof Class) {
                // 传参和方法参数定义中有基本类型，不处理
                if (args[i].getClass().isPrimitive()) {
                    continue;
                }
                if (((Class)types[i]).isPrimitive()) {
                    continue;
                }
            }

            if (types[i] != args[i].getClass()) {
                 jsonObject.set(String.valueOf(i), args[i].getClass().getName());
            }
        }
        return jsonObject;
    }

    public static Object[] JsonObject2MethodParameters(Method method, Object json, JSONObject paramsTypes) throws BizException {
        int parameterCount = method.getParameterCount();
        Type[] parameterTypes = method.getGenericParameterTypes();
        if (paramsTypes != null) {
            for (Object key : paramsTypes.keySet()) {
                int index = Integer.valueOf((String)key);
                try {
                    parameterTypes[index] = Class.forName((String)paramsTypes.get(key));
                } catch (ClassNotFoundException e) {
                    throw new BizException(BizResultEnum.OTHER_CLASS_NOTFOUND);
                }
            }
        }
        Object[] parameterObjects = new Object[parameterCount];
        if (parameterCount == 0) {
            return parameterObjects;
        } else {
            if (!(json instanceof JSONArray)) {
                throw new BizException(BizResultEnum.OTHER_JAVA_API_PARAMETER_ERROR, "输入参数应为JSONArray类型");
            }
            JSONArray jsonParams = (JSONArray) json;
            if (jsonParams.size() < parameterCount) {
                throw new BizException(BizResultEnum.OTHER_JAVA_API_PARAMETER_ERROR, "输入参数小于约定参数个数");
            }
            for (int i = 0; i < parameterCount; i++) {
                parameterObjects[i] = Json2Bean(jsonParams.get(i), parameterTypes[i]);
            }
            return parameterObjects;
        }

    }
    public static Object methodReturnBean2Json(Object returnValue) {
        if (returnValue == null) {
            return null;
        } else if (returnValue instanceof List || returnValue instanceof Object[]) {
            return JSONUtil.parseArray(returnValue);
        } else if (ObjectUtil.isBasicType(returnValue) || returnValue instanceof String) {
            return returnValue;
        } else {
            return JSONUtil.parseObj(returnValue);
        }
    }

    public static Object methodReturnJSON2Bean(Method method, Object result) {
        if (result == null) {
            return null;
        }
        Type returnType = method.getGenericReturnType();
        return Convert.convert(returnType,result);
    }
}
